#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <linux/videodev2.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <unistd.h>
#include <errno.h>
#include <SDL2/SDL.h>
#include <SDL2/SDL_syswm.h>

#define EGL_EGLEXT_PROTOTYPES
#define GL_GLEXT_PROTOTYPES

#include <EGL/egl.h>
#include <EGL/eglext.h>
#include <GLES2/gl2.h>
#include <GLES2/gl2ext.h>

#include <libdrm/drm_fourcc.h>

#define VIDEO_DEV "/dev/video10"
#define WIDTH 1920
#define HEIGHT 1080
#define BUFFER_COUNT 4

struct buffer {
    int fd;
};

SDL_Window *window;
EGLSurface surface;
SDL_GLContext ctx;
EGLDisplay eglDisplay;
GLuint texture;

GLfloat verts[] = {
    -1, -1, 0, 1,
    1, -1, 1, 1,
    -1, 1, 0, 0,
    1, 1, 1, 0,
};

static const char *vs_src =
    "attribute vec4 position;\n"
    "attribute vec2 texcoord;\n"
    "varying vec2 v_texcoord;\n"
    "void main() {\n"
    "  gl_Position = position;\n"
    "  v_texcoord = texcoord;\n"
    "}";

static const char *fs_src =
    "#extension GL_OES_EGL_image_external : require\n"
    "precision mediump float;\n"
    "uniform samplerExternalOES tex;\n"
    "varying vec2 v_texcoord;\n"
    "void main() {\n"
    "  gl_FragColor = texture2D(tex, v_texcoord);\n"
    "}";

// 检查 ioctl 是否出错
static void xioctl(int fd, int request, void *arg) {
    if (ioctl(fd, request, arg) < 0) {
        perror("ioctl");
        exit(1);
    }
}

GLuint loadShader(GLenum type, const char *src) {
    GLuint s = glCreateShader(type);
    glShaderSource(s, 1, &src, NULL);
    glCompileShader(s);
    return s;
}

int initDisplay() {
    // SDL2 初始化
    SDL_Init(SDL_INIT_VIDEO);
    window = SDL_CreateWindow("DMABUF Viewer", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED,
                                           WIDTH, HEIGHT, SDL_WINDOW_OPENGL);

    SDL_SysWMinfo wminfo;
    SDL_VERSION(&wminfo.version);
    SDL_GetWindowWMInfo(window, &wminfo);
//    Display *x_display = wminfo.info.x11.display;
    Display *x_display = XOpenDisplay(NULL);
    Window x_window = wminfo.info.x11.window;

    // EGL 初始化
    eglDisplay = eglGetDisplay((EGLNativeDisplayType)x_display);
    eglInitialize(eglDisplay, NULL, NULL);

    EGLint config_attribs[] = {
        EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
        EGL_RED_SIZE,   8,
        EGL_GREEN_SIZE, 8,
        EGL_BLUE_SIZE,  8,
        EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
        EGL_NONE
    };

    EGLConfig cfg;
    EGLint n;
    eglChooseConfig(eglDisplay, config_attribs, &cfg, 1, &n);

    surface = eglCreateWindowSurface(eglDisplay, cfg, (EGLNativeWindowType)x_window, NULL);

    EGLint ctx_attr[] = {
        EGL_CONTEXT_CLIENT_VERSION, 2,
        EGL_NONE
    };
    ctx = eglCreateContext(eglDisplay, cfg, EGL_NO_CONTEXT, ctx_attr);
    eglMakeCurrent(eglDisplay, surface, surface, ctx);

    GLuint vs = loadShader(GL_VERTEX_SHADER, vs_src);
    GLuint fs = loadShader(GL_FRAGMENT_SHADER, fs_src);

    GLuint program = glCreateProgram();
    glAttachShader(program, vs);
    glAttachShader(program, fs);
    glBindAttribLocation(program, 0, "position");
    glBindAttribLocation(program, 1, "texcoord");
    glLinkProgram(program);
    glUseProgram(program);
    glUniform1i(glGetUniformLocation(program, "tex"), 0);

    glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(GLfloat), verts);
    glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(GLfloat), verts + 2);
    glEnableVertexAttribArray(0);
    glEnableVertexAttribArray(1);

    glGenTextures(1, &texture);
//    const char *extensions = eglQueryString(eglDisplay, EGL_EXTENSIONS);
    return 0;
}

int display(int dmabuf_fd) {
    PFNEGLCREATEIMAGEKHRPROC eglCreateImageKHR =
        (PFNEGLCREATEIMAGEKHRPROC)eglGetProcAddress("eglCreateImageKHR");
    PFNGLEGLIMAGETARGETTEXTURE2DOESPROC glEGLImageTargetTexture2DOES =
        (PFNGLEGLIMAGETARGETTEXTURE2DOESPROC)eglGetProcAddress("glEGLImageTargetTexture2DOES");
    PFNEGLDESTROYIMAGEKHRPROC eglDestroyImageKHR =
        (PFNEGLDESTROYIMAGEKHRPROC)eglGetProcAddress("eglDestroyImageKHR");

    // 创建 EGLImage 来导入 dma-buf
    EGLAttrib img_attrs[] = {
        EGL_WIDTH, WIDTH,
        EGL_HEIGHT, HEIGHT,
        EGL_LINUX_DRM_FOURCC_EXT, DRM_FORMAT_NV12, // 使用 DRM FourCC 格式
        EGL_DMA_BUF_PLANE0_FD_EXT, dmabuf_fd,
        EGL_DMA_BUF_PLANE0_OFFSET_EXT, 0,
        EGL_DMA_BUF_PLANE0_PITCH_EXT, WIDTH,
        EGL_DMA_BUF_PLANE1_FD_EXT, dmabuf_fd,
        EGL_DMA_BUF_PLANE1_OFFSET_EXT, WIDTH * 1088,
        EGL_DMA_BUF_PLANE1_PITCH_EXT, WIDTH,
        EGL_NONE
    };
    EGLImageKHR image = eglCreateImageKHR(eglDisplay, EGL_NO_CONTEXT, EGL_LINUX_DMA_BUF_EXT, NULL, img_attrs);
    if (image == EGL_NO_IMAGE_KHR) {
        fprintf(stderr, "eglCreateImageKHR failed\n");
        return 1;
    }

    // 绑定为 OpenGL 纹理
    glBindTexture(GL_TEXTURE_EXTERNAL_OES, texture);
    glEGLImageTargetTexture2DOES(GL_TEXTURE_EXTERNAL_OES, image);

    glViewport(0,0, WIDTH, HEIGHT);
    // 绘制这个纹理（使用一个简单的 OpenGL ES2 渲染器）
    glClearColor(0, 0, 0, 1);
    glClear(GL_COLOR_BUFFER_BIT);

    // 注意：实际要绘制该纹理，你需要设置 Shader + 绑定纹理坐标（此处省略）
    // 这里直接清屏，仅演示 eglImage 导入流程
    glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);

    eglSwapBuffers(eglDisplay, surface);
    eglDestroyImageKHR(eglDisplay, image);
    return 0;
}

int destroyDisplay() {
    // 清理资源
    glDeleteTextures(1, &texture);
    eglTerminate(eglDisplay);
    SDL_GL_DeleteContext(ctx);
    SDL_DestroyWindow(window);
    SDL_Quit();
    return 0;
}

__u32 getNalu(void *start) {
    static char buffer[20*1024*1024];
    static size_t length = 0;
    static size_t pos = 0;

    if (length == 0) {
        FILE *fp = fopen("input.h264", "rb");
        if (!fp) {
            perror("fopen");
            return 0;
        }

        length = fread(buffer, 1, 20*1024*1024, fp);
        fclose(fp);
    }

    if (pos == length) return 0;

    size_t start_pos = pos;
    pos += 4;

    char flag = 4;
    while (pos < length) {
        if (buffer[pos] == 0) {
            flag >>= 1;
        } else if (buffer[pos] == 1 && flag == 0) {
            pos -= 3;
            break;
        } else {
            flag = 4;
        }
        pos++;
    }

    memcpy(start, &buffer[start_pos], pos - start_pos);
    return pos - start_pos;
}

int main() {
    int fd = open(VIDEO_DEV, O_RDWR|O_NONBLOCK);
    if (fd < 0) {
        perror("open");
        return 1;
    }

    // 设置 OUTPUT（压缩流）格式
    struct v4l2_format fmt = {0};
    fmt.type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE;
    fmt.fmt.pix_mp.width = WIDTH;
    fmt.fmt.pix_mp.height = HEIGHT;
    fmt.fmt.pix_mp.pixelformat = V4L2_PIX_FMT_H264;
    fmt.fmt.pix_mp.num_planes = 1;
    xioctl(fd, VIDIOC_S_FMT, &fmt);

    // 请求 OUTPUT buffer
    struct v4l2_requestbuffers req = {
        .count = 1,
        .type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE,
        .memory = V4L2_MEMORY_MMAP
    };
    xioctl(fd, VIDIOC_REQBUFS, &req);

    // mmap output buffers
    struct v4l2_buffer output_buf = {
        .type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE,
        .memory = V4L2_MEMORY_MMAP,
        .index = 0
    };
    struct v4l2_plane output_planes[1];

    output_buf.length = 1;
    output_buf.m.planes = output_planes;
    xioctl(fd, VIDIOC_QUERYBUF, &output_buf);

    void *output_start = mmap(NULL, output_buf.m.planes[0].length, PROT_READ | PROT_WRITE, MAP_SHARED, fd, output_buf.m.planes[0].m.mem_offset);

    // 启动 OUTPUT 流
    enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE;
    xioctl(fd, VIDIOC_STREAMON, &type);

    // 设置 CAPTURE（解码帧）格式
    fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
    fmt.fmt.pix_mp.width = WIDTH;
    fmt.fmt.pix_mp.height = HEIGHT;
    fmt.fmt.pix_mp.pixelformat = V4L2_PIX_FMT_NV12;
    fmt.fmt.pix_mp.num_planes = 1;
    xioctl(fd, VIDIOC_S_FMT, &fmt);

    // 请求 CAPTURE buffer
    struct v4l2_requestbuffers req2 = {
        .count = BUFFER_COUNT,
        .type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE,
        .memory = V4L2_MEMORY_MMAP
    };
    xioctl(fd, VIDIOC_REQBUFS, &req2);

    // mmap capture buffers
    struct buffer capture_buffers[BUFFER_COUNT];
    for (int i = 0; i < BUFFER_COUNT; i++) {
        struct v4l2_buffer buf = {
            .type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE,
            .memory = V4L2_MEMORY_DMABUF,
            .index = i
        };
        struct v4l2_plane planes[1];
        buf.length = 1;
        buf.m.planes = planes;
        xioctl(fd, VIDIOC_QUERYBUF, &buf);

        struct v4l2_exportbuffer expbuf;
        memset(&expbuf, 0, sizeof(expbuf));
        expbuf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
        expbuf.index = i;
        expbuf.plane = 0;
        xioctl(fd, VIDIOC_EXPBUF, &expbuf);
        capture_buffers[i].fd = expbuf.fd;

        xioctl(fd, VIDIOC_QBUF, &buf);
    }

    // 启动 CAPTURE 流
    type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
    xioctl(fd, VIDIOC_STREAMON, &type);

    // 准备 SDL2 显示
    initDisplay();

    // 发送数据到解码器并显示输出帧
    while (1) {
        // 读取一帧数据（这里简单假设每次读取 4KB）
        output_planes[0].bytesused = getNalu(output_start);
        if (output_planes[0].bytesused <= 0) break;

        // 提交 H.264 数据
        if (ioctl(fd, VIDIOC_QBUF, &output_buf) < 0) {
            perror("VIDIOC_QBUF (output)");
            continue;
        }

        // 获取解码帧
        struct v4l2_buffer cbuf = {0};
        struct v4l2_plane cplanes[1];
        cbuf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
        cbuf.memory = V4L2_MEMORY_MMAP;
        cbuf.m.planes = cplanes;
        cbuf.length = 1;

        while (ioctl(fd, VIDIOC_DQBUF, &cbuf) == 0) {
            // 将 NV12 数据显示
            display(capture_buffers[cbuf.index].fd);

            // 重新放入 buffer
            xioctl(fd, VIDIOC_QBUF, &cbuf);

            SDL_Delay(33); // 简单限速
        }

        // dequeue output buffer
        while (ioctl(fd, VIDIOC_DQBUF, &output_buf) < 0) {
            SDL_Delay(5); // 简单限速
        }

        SDL_PollEvent(NULL); // 允许关闭窗口
        SDL_Delay(5); // 简单限速
    }

    close(fd);
    destroyDisplay();
    return 0;
}
